package com.huaxonline.boot.web.sign;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.ContentType;
import com.huaxonline.boot.cache.redis.template.RedisUtil;
import com.huaxonline.boot.web.constant.enums.ArgumentResponseEnum;
import com.huaxonline.boot.web.constant.enums.ServletResponseEnum;
import com.huaxonline.boot.web.exception.BusinessException;
import com.huaxonline.boot.web.util.WebUtil;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 签名模板类 - 优化版本
 * @author zhaoshulei
 * @since 2023/9/18
 */
@Slf4j
public class SignTemplate {
    private RedisUtil redisUtil;
    private SignConfig signConfig;
    private Map<String, String> apiKey = new ConcurrentHashMap<>();
    private SignAlgorithm signAlgorithm;
    
    // 使用常量类中的定义
    public static final String APP_ID = SignConstants.HEADER_APP_ID;
    public static final String SECRET = SignConstants.HEADER_SECRET;
    public static final String TIMESTAMP = SignConstants.HEADER_TIMESTAMP;
    public static final String NONCE = SignConstants.HEADER_NONCE;
    public static final String SIGN = SignConstants.HEADER_SIGNATURE;
    public static final String HASHED_REQUEST_PAYLOAD = SignConstants.HEADER_HASHED_REQUEST_PAYLOAD;

    public static SignTemplate getInstance() {
        return SignTemplateHolder.SIGN_TEMPLATE;
    }

    private static class SignTemplateHolder {
        private static final SignTemplate SIGN_TEMPLATE = new SignTemplate();
    }

    public void setRedisUtil(RedisUtil redisUtil) {
        this.redisUtil = redisUtil;
    }

    public void setSignConfig(SignConfig signConfig) {
        this.signConfig = signConfig;
        this.signAlgorithm = SignAlgorithm.fromName(signConfig.getAlgorithm());
    }

    public void setApiKey(Map<String, String> apiKey) {
        this.apiKey.clear();
        this.apiKey.putAll(apiKey);
    }

    public void checkRequest() {
        HttpServletRequest request = WebUtil.getRequest();
        if (ObjectUtil.isNull(request)) {
            throw new BusinessException(ServletResponseEnum.MissingServletRequestException);
        }
        
        // 获取客户端IP
        String clientIp = getClientIp(request);
        
        // 验证必要的请求头
        String appId = request.getHeader(APP_ID);
        String timestamp = request.getHeader(TIMESTAMP);
        String nonce = request.getHeader(NONCE);
        String signature = request.getHeader(SIGN);
        
        ArgumentResponseEnum.APP_ID_NOT_BLANK.assertIsFalse(StrUtil.isBlank(appId));
        ArgumentResponseEnum.TIMESTAMP_NOT_BLANK.assertIsFalse(StrUtil.isBlank(timestamp));
        ArgumentResponseEnum.NONCE_NOT_BLANK.assertIsFalse(StrUtil.isBlank(nonce));
        ArgumentResponseEnum.SIGN_NOT_BLANK.assertIsFalse(StrUtil.isBlank(signature));
        
        // 获取密钥
        String secret = getSecret(appId);
        ArgumentResponseEnum.APP_ID_NO_PERMISSION.assertIsFalse(StrUtil.isBlank(secret));
        
        // IP白名单检查
//        if (!signConfig.isIpAllowed(clientIp)) {
//            log.warn("IP {} 不在白名单中，AppId: {}", clientIp, appId);
//            throw new BusinessException(ServletResponseEnum.Forbidden);
//        }
//
//        // 请求频率限制检查
//        if (!signConfig.checkRateLimit(appId, clientIp)) {
//            log.warn("AppId {} 请求频率超限，IP: {}", appId, clientIp);
//            throw new BusinessException(ServletResponseEnum.TooManyRequests);
//        }
//
//        // 签名重放检测
//        if (!signConfig.checkReplay(appId, signature)) {
//            log.warn("检测到签名重放攻击，AppId: {}, IP: {}, Signature: {}", appId, clientIp, signature);
//            throw new BusinessException(ServletResponseEnum.SignatureReplay);
//        }
        
        // 构建参数映射
        Map<String, String> paramMap = new HashMap<>();
        paramMap.put(APP_ID, appId);
        paramMap.put(TIMESTAMP, timestamp);
        paramMap.put(NONCE, nonce);
        paramMap.put(SIGN, signature);
        
        // 根据请求方法处理参数
        if (ServletUtil.METHOD_GET.equals(request.getMethod())) {
            this.getParamMap(paramMap, request);
        } else if (ServletUtil.METHOD_POST.equals(request.getMethod())) {
            this.postParamParamMap(paramMap, request);
        } else {
            throw new BusinessException(ServletResponseEnum.HttpRequestMethodNotSupportedException);
        }
        
        // 验证参数
        this.checkParamMap(paramMap);
        
        if (signConfig.getDebugMode()) {
            log.info("签名验证通过，AppId: {}, IP: {}, Timestamp: {}", appId, clientIp, timestamp);
        }
    }

    public void getParamMap(Map<String, String> hashMap, HttpServletRequest request) {
        Map<String, String[]> parameterMap = request.getParameterMap();
        for (String key : parameterMap.keySet()) {
            String[] value = (String[]) parameterMap.get(key);
            hashMap.put(key, value[0]);
        }
    }


    public void postParamParamMap(Map<String, String> hashMap, HttpServletRequest request) {
        if (signConfig.getEnableBodySign() && ContentType.JSON.getValue().equals(request.getContentType())) {
            String bodyString = ((BodyReaderHttpServletRequestWrapper) request).getBodyString();
            String bodyHash = signAlgorithm.sign(bodyString, "");
            hashMap.put(HASHED_REQUEST_PAYLOAD, bodyHash);
        } else {
            this.getParamMap(hashMap, request);
        }
    }
    
    /**
     * 获取密钥，支持缓存
     */
    private String getSecret(String appId) {
        // 先从内存缓存获取
        String secret = apiKey.get(appId);
        if (StrUtil.isNotBlank(secret)) {
            return secret;
        }
        
        // 从Redis缓存获取
        if (redisUtil != null) {
            String cacheKey = SignConstants.REDIS_SECRET_PREFIX + appId;
            secret = (String) redisUtil.get(cacheKey);
            if (StrUtil.isNotBlank(secret)) {
                apiKey.put(appId, secret); // 更新内存缓存
                return secret;
            }
        }
        
        return null;
    }


    public void checkParamMap(Map<String, String> paramMap) {
        String timestampValue = (String) paramMap.get(TIMESTAMP);
        String nonceValue = (String) paramMap.get(NONCE);
        String signValue = (String) paramMap.get(SIGN);
        if (this.signConfig.getIsCheckTimestamp()) {
            this.checkTimestamp(Long.parseLong(timestampValue));
        }
        if (this.signConfig.getIsCheckNonce()) {
            this.checkNonce(paramMap, nonceValue);
        }
        this.checkSign(paramMap, signValue);
    }

    public void checkTimestamp(long timestamp) {
        if (!this.isValidTimestamp(timestamp)) {
            ArgumentResponseEnum.TIMESTAMP_EXCEED_LIMIT.assertFail(timestamp);
        }
    }

    public boolean isValidTimestamp(long timestamp) {
        long allowDisparity = this.signConfig.getTimestampDisparity();
        long disparity = Math.abs(System.currentTimeMillis() - timestamp);
        return allowDisparity == -1L || disparity <= allowDisparity;
    }

    public void checkNonce(Map<String, String> paramMap, String nonce) {
        if (redisUtil == null) {
            log.warn("Redis未配置，跳过nonce重复检查");
            return;
        }
        
        String key = SignConstants.REDIS_NONCE_PREFIX + paramMap.get(APP_ID) + ":" + nonce;
        if (this.redisUtil.get(key) != null) {
            ArgumentResponseEnum.NONCE_REPEATED.assertFail(nonce);
        } else {
            this.redisUtil.setEx(key, nonce, getSaveNonceExpire() * SignConstants.DEFAULT_NONCE_EXPIRE_MULTIPLIER + 2L, TimeUnit.SECONDS);
        }
    }

    public long getSaveNonceExpire() {
        return this.signConfig.getTimestampDisparity() >= 0L ? this.signConfig.getTimestampDisparity() / 1000L : 86400L;
    }

    public void checkSign(Map<String, ?> paramsMap, String sign) {
        if (!this.isValidSign(paramsMap, sign)) {
            ArgumentResponseEnum.SIGN_INVALID.assertFail();
        }
    }

    public boolean isValidSign(Map<String, ?> paramsMap, String sign) {
        String theSign = this.createSign(paramsMap);
        return theSign.equals(sign);
    }

    public String createSign(Map<String, ?> paramsMap) {
        String appId = (String) paramsMap.get(APP_ID);
        String secret = getSecret(appId);
        ArgumentResponseEnum.APP_ID_NO_PERMISSION.assertIsFalse(StrUtil.isBlank(secret));
        
        // 移除签名参数
        Map<String, Object> signParams = new TreeMap<>(paramsMap);
        signParams.remove(SIGN);
        
        String paramsStr = this.joinParamsDictSort(signParams);
        String fullStr = SECRET + "=" + secret + "&" + paramsStr;
        
        if (signConfig.getDebugMode()) {
            log.info("签名字符串: {}", fullStr);
        }
        
        return signAlgorithm.sign(fullStr, secret);
    }


    public String joinParamsDictSort(Map<String, ?> paramsMap) {
        if (!(paramsMap instanceof TreeMap)) {
            paramsMap = new TreeMap((Map) paramsMap);
        }
        return this.joinParams((Map) paramsMap);
    }

    public String joinParams(Map<String, ?> paramsMap) {
        StringBuilder sb = new StringBuilder();
        Iterator var3 = paramsMap.keySet().iterator();
        while (var3.hasNext()) {
            String key = (String) var3.next();
            Object value = paramsMap.get(key);
            if (!ObjectUtil.isEmpty(value)) {
                sb.append(key).append("=").append(value).append("&");
            }
        }
        if (sb.length() > 0) {
            sb.deleteCharAt(sb.length() - 1);
        }
        return sb.toString();
    }

    /**
     * 生成签名（兼容旧方法）
     */
    public String abstractStr(String fullStr) {
        return signAlgorithm.sign(fullStr, "");
    }
    
    /**
     * 生成签名（新方法）
     */
    public String generateSign(String fullStr, String secret) {
        return signAlgorithm.sign(fullStr, secret);
    }
    
    /**
     * 获取客户端IP地址
     */
    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        
        // 处理多个IP的情况，取第一个
        if (StrUtil.isNotBlank(ip) && ip.contains(",")) {
            ip = ip.split(",")[0].trim();
        }
        
        return ip;
    }
}
